package LiuYun

import spinal.core._
import spinal.lib._


class L1TLBReq(cat:Int) extends Bundle
{
  val vaddr  = LISA.GPR
  val st  = ifGen(cat==LISA.TLB.DATA)(Bool())
}

class L1TLB(cat:Int) extends Component
{
  val io = new Bundle{
    val csr  = in(new TlbCsr)
    val from = new Bundle{
      val srch   = slave(Flow(new L1TLBReq(cat)))
      val l2_tlb = slave(new L2ToL1TLB())
    }
    val to = new Bundle{
      val resp  = out(new AddrTransResp())
    }
  }

  val csr    = io.csr
  val manage = io.from.l2_tlb

  val entry_hi = Vec(new TlbEHi, LISA.L1TLB.CAP) setAsReg()
  val entry_lo = Vec(Vec(HardType(new CsrTLBELONoG), 2), LISA.L1TLB.CAP) setAsReg()

  for(i <- 0 until LISA.L1TLB.CAP){
    entry_hi(i).e.init(False)
  }

  val s0 = new Area{
    val req       = io.from.srch

    val src_vppn_basic = req.vaddr(LISA.VALEN-1 downto LISA.BASIC_PS+1)
    val src_vppn_huge  = req.vaddr(LISA.VALEN-1 downto LISA.HUGE_PS +1)

    val tgt_vppn_basic = Vec(UInt(LISA.TLB.VPPN_LEN      bits), LISA.L1TLB.CAP)
    val tgt_vppn_huge  = Vec(UInt(LISA.TLB.VPPN_LEN_HUGE bits), LISA.L1TLB.CAP)

    val check_exist  = Bits(LISA.L1TLB.CAP bits)
    val check_tgt    = Bits(LISA.L1TLB.CAP bits)
    val check_field  = Bits(LISA.L1TLB.CAP bits)
    val entry_hit    = Bits(LISA.L1TLB.CAP bits)

    for(i <- 0 until LISA.L1TLB.CAP){
      tgt_vppn_basic(i) := entry_hi(i).vppn.takeHigh(LISA.TLB.VPPN_LEN_BASIC).asUInt
      tgt_vppn_huge (i) := entry_hi(i).vppn.takeHigh(LISA.TLB.VPPN_LEN_HUGE ).asUInt

      check_exist(i) := entry_hi(i).e
      check_tgt  (i) := Mux(entry_hi(i).ps, src_vppn_huge ===tgt_vppn_huge (i),
                                            src_vppn_basic===tgt_vppn_basic(i))
      check_field(i) := entry_hi(i).g || csr.asid.asid === entry_hi(i).asid

      entry_hit(i) := check_exist(i) &&
                      check_tgt  (i) &&
                      check_field(i)
    }

    val new_coming = new Bundle{
      val vppn_basic = manage.entry.hi.vppn.takeHigh(LISA.TLB.VPPN_LEN_BASIC).asUInt
      val vppn_huge  = manage.entry.hi.vppn.takeHigh(LISA.TLB.VPPN_LEN_HUGE ).asUInt

      val check_exist = manage.entry.hi.e
      val check_tgt   = Mux(manage.entry.hi.ps, src_vppn_huge ===vppn_huge,
                                                src_vppn_basic===vppn_basic)
      val check_field = manage.entry.hi.g || csr.asid.asid === manage.entry.hi.asid

      val hit = check_exist &&
                check_tgt   &&
                check_field
    }

    for(i <- 0 until LISA.L1TLB.CAP){
      when(manage.invalidate && manage.idx(i)){
        entry_hi(i).e := False
      }.elsewhen(manage.fillback && manage.idx(i)){
        entry_hi(i) := manage.entry.hi
      }
      when(manage.fillback && manage.idx(i)){
        entry_lo(i) := manage.entry.lo
      }
    }
/*
    assert(
      assertion = IsOneHot.or0(entry_hit),
      message   = "[L1TLB] multi way hit in L1TLB!",
      severity  = FAILURE
    )
*/
  }

  val s1 = new Area{
    val valid = RegInit(False)
    val vaddr = UInt(LISA.VADDR_WIDTH bits) setAsReg()
    val st = ifGen(cat==LISA.TLB.DATA)(
      Bool() setAsReg()
    )
    val hit_mask = Bits(LISA.L1TLB.CAP bits) setAsReg()

    valid    := s0.req.valid
    vaddr    := s0.req.vaddr
    hit_mask := s0.entry_hit
    if(cat==LISA.TLB.DATA   ){ st := s0.req.st }

    val new_coming = new Area{
      val valid    = RegInit(False)
      val hit      = RegInit(False)
      val idx      = Bits(LISA.L1TLB.CAP bits) setAsReg()
      val entry_lo = Vec(HardType(new CsrTLBELONoG), 2) setAsReg()
      val entry_ps = RegInit(False)
      val rw_conf  = Bool()

      valid    := manage.fillback
      hit      := s0.new_coming.hit && manage.fillback
      idx      := manage.idx
      entry_lo := manage.entry.lo
      entry_ps := manage.entry.hi.ps
      rw_conf  := valid && idx === hit_mask
    }

    val hit_entry_ps = MuxOH.or(hit_mask, entry_hi.map(_.ps))
    val hit_entry_lo = MuxOH.or(hit_mask, entry_lo)

    val ps       = Bool()
    val hit      = Bool()
    val sel      = Bool()
    val found_lo = new CsrTLBELONoG
    val ptag     = UInt(LISA.TAG_WIDTH bits)

    hit := new_coming.hit || hit_mask.orR && !new_coming.rw_conf
    when(new_coming.hit){
      ps       := new_coming.entry_ps
      sel      := Mux(new_coming.entry_ps, vaddr(LISA.HUGE_PS),
                                           vaddr(LISA.BASIC_PS))
      found_lo := new_coming.entry_lo(sel.asUInt)
    }.otherwise{
      ps       := hit_entry_ps
      sel      := Mux(hit_entry_ps, vaddr(LISA.HUGE_PS),
                                    vaddr(LISA.BASIC_PS))
      found_lo := hit_entry_lo(sel.asUInt)
    }

    when(ps){
      ptag := found_lo.ppn.dropLow(LISA.HUGE_PS - LISA.BASIC_PS).asUInt @@
              vaddr(LISA.HUGE_PS-1 downto LISA.TAG_LO)
    }.otherwise{
      ptag := found_lo.ppn @@ vaddr(LISA.BASIC_PS-1 downto LISA.TAG_LO)
    }

    val mat   = found_lo.mat
    val ex    = Bool()
    val ecode = LISA.Ex.Code
    val esubc = LISA.Ex.Subc

    // val page_ade     = csr.crmd.plv === U(3) && vaddr.msb
    val page_invalid = hit && !found_lo.v
    val page_ppi     = hit && found_lo.plv < csr.crmd.plv
    val page_modify  = ifGen(cat==LISA.TLB.DATA){ hit && !found_lo.d && st}

    if(cat==LISA.TLB.FETCH){
      // ex := page_ade || page_invalid || page_ppi
      ex := page_invalid || page_ppi
      // when(page_ade){
      //   ecode := LISA.Ex.ADEF.code
      //   esubc := LISA.Ex.ADEF.subc
      // }.else
      when(page_invalid){
        ecode := LISA.Ex.PIF.code
        esubc := LISA.Ex.PIF.subc
      }.elsewhen(page_ppi){
        ecode := LISA.Ex.PPI.code
        esubc := LISA.Ex.PPI.subc
      }.otherwise{
        ecode := U(0)
        esubc := U(0)
      }
    }else{
      // ex := page_ade || page_invalid || page_ppi || page_modify
      ex := page_invalid || page_ppi || page_modify
      // when(page_ade){
      //   ecode := LISA.Ex.ADEM.code
      //   esubc := LISA.Ex.ADEM.subc
      // }.else
      when(page_invalid){
        when(st){
          ecode := LISA.Ex.PIS.code
          esubc := LISA.Ex.PIS.subc
        }.otherwise{
          ecode := LISA.Ex.PIL.code
          esubc := LISA.Ex.PIL.subc
        }
        
      }.elsewhen(page_ppi){
        ecode := LISA.Ex.PPI.code
        esubc := LISA.Ex.PPI.subc
      }.elsewhen(page_modify){
        ecode := LISA.Ex.PME.code
        esubc := LISA.Ex.PME.subc
      }.otherwise{
        ecode := U(0)
        esubc := U(0)
      }
    }

}
  io.to.resp.hit   := s1.hit && s1.valid
  io.to.resp.pt    := s1.ptag
  io.to.resp.mat   := s1.mat
  io.to.resp.ex    := s1.ex
  io.to.resp.ecode := s1.ecode
  io.to.resp.esubc := s1.esubc


}